Skip to content

Allow string keys in eval utility #242

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 7 commits into from
Mar 22, 2023
Merged

Conversation

Raj-Parekh24
Copy link
Contributor

Motivation for these changes

Closes #227

Implementation details

I have implemented a function that converts the dictionary with string keys to pytensor. The pytensor variable for the corresponding name is found using the function "get_var_by_name".

Checklist

Major / Breaking Changes

  • ...

New features

  • ... Its and enhancement of current functionality eval present in graph/basic.py under Variable class. Through this enhancement eval will accept string i.e. pytensor node name.

Bugfixes

  • ...

Documentation

  • ...

Maintenance

  • ...

@ricardoV94 ricardoV94 added the enhancement New feature or request label Mar 11, 2023
@ricardoV94 ricardoV94 changed the title Added functionality that can use name of variable in eval Allow string keys in eval utility Mar 11, 2023
Copy link
Member

@ricardoV94 ricardoV94 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great @Raj-Parekh24

I left some suggestions below

@Raj-Parekh24
Copy link
Contributor Author

Raj-Parekh24 commented Mar 11, 2023

While running pre-commit run --all in the local machine I am getting a series of errors with the last message as 1 files unexpectedly failed. pytensor/link/c/exceptions.py These files did not fail before, so please check the above output for errors in {'pytensor/link/c/exceptions.py'} and fix them. You can run python scripts/run_mypy.py --verbose to reproduce this test locally.

When I check its logs for basic.py there are no errors from my changes.

Is this expected behavior or I have broken something?

I am using python 3.11

@Raj-Parekh24
Copy link
Contributor Author

And in the last build, the test case failed for multiple environments is:
FAILED tests/link/jax/test_scalar.py::test_erfinv - AssertionError:

@ricardoV94
Copy link
Member

And in the last build, the test case failed for multiple environments is:
FAILED tests/link/jax/test_scalar.py::test_erfinv - AssertionError:

Yeah needs to be investigated. Probably a new release of jax that behaves differently.

The tracking issue is #241

@ricardoV94
Copy link
Member

Mypy doesn't work on python 3.11, issue here #240

@Raj-Parekh24
Copy link
Contributor Author

Thanks for the help @ricardoV94 ,

I have updated the code and added warning when multiple variables are there for same name.

And also updated the test cases accordingly.

Please review it.

Comment on lines 589 to 591
warnings.warn(
f"Found {length_of_nodes_with_matching_names} pytensor variables with name {i} taking the first declared named variable for computation"
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems safer to just fail.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of warning will throw Exception instead.

Comment on lines 572 to 578
>>> import numpy as np
>>> import pytensor.tensor as at
>>> x = at.dscalar('x')
>>> y = at.dscalar('y')
>>> z = x + y
>>> np.allclose(z.eval({'x' : 3, 'y' : 1}), 4)
True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not show a use of the function. In any case we don't need a docstring with an example I think

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are making it internal to eval I also think that the doc-string will be no more required.

@@ -558,6 +558,46 @@ def get_parents(self):
return [self.owner]
return []

def convert_string_keys_to_pytensor_variables(self, inputs_to_values):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given we are in pytensor, no need to mention it in the name. If you want you can specify the return type is Dict[Variable, Variable]

Suggested change
def convert_string_keys_to_pytensor_variables(self, inputs_to_values):
def convert_string_keys_to_variables(self, inputs_to_values):

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good, will replace the function name with suggested one.

assert self.w.eval({self.z: 3}) == 6.0

def test_eval_with_strings_with_mulitple_same_name(self):
assert self.t.eval({"e": 1.0}) == 2.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would create the new variables here instead of setup_method. It's arguably more readable and these are unlikely to be used in other tests

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, instead of creating variables in setup_method will add the variables in test_function.

@ricardoV94
Copy link
Member

@Raj-Parekh24 nice work. I left some comments above. Let me know what you think.

@Raj-Parekh24
Copy link
Contributor Author

Hi @ricardoV94 ,

Thanks for the review and the suggestions, I have updated the implementation accordingly and pushed a new commit regarding it. Please review it and suggest the required changes.

@Raj-Parekh24
Copy link
Contributor Author

Hey @ricardoV94 ,

I have updated the code bases on your suggestions.

And I also want to work on issue #55, I got the requirements but still not able to understand how to implement it. Since I am new to the repo, can you please give me starting steps to work on it.

@ricardoV94
Copy link
Member

Hi @Raj-Parekh24 sorry for the delay. I'll try to review your PR tomorrow. For that other PR I will give some more pointers there

Thanks for all the work!

@Raj-Parekh24
Copy link
Contributor Author

Thanks for all the help and support @ricardoV94 .

Copy link
Member

@ricardoV94 ricardoV94 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I left some suggestions below

Comment on lines 614 to 616
nodes_with_matching_names[
length_of_nodes_with_matching_names - 1
]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can just be nodes_with_matching_names[0], now that you checked only 1 case exists.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your review @ricardoV94. Will update the index value.

@Raj-Parekh24
Copy link
Contributor Author

Thanks a lot for your review @ricardoV94,

I have added the code based on what you suggested. Please review it and kindly give your suggestions.

Copy link
Member

@ricardoV94 ricardoV94 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functionality-wise this PR is ready! I just left some comments on code formatting. Let me know if you have any questions.

assert self.w.eval({"x": 1.0, self.y: 2.0}) == 6.0
assert self.w.eval({self.z: 3}) == 6.0

def test_eval_errors_having_mulitple_variables_same_name(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def test_eval_errors_having_mulitple_variables_same_name(self):
def test_eval_with_strings_multiple_matches(self):

with pytest.raises(Exception, match="Found 2 pytensor variables with name e"):
t.eval({"e": 1})

def test_eval_errors_with_no_name_exists(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def test_eval_errors_with_no_name_exists(self):
def test_eval_with_strings_no_match(self):

Comment on lines 600 to 620
def convert_string_keys_to_variables():
process_input_to_values = {}
for i in inputs_to_values:
if isinstance(i, str):
nodes_with_matching_names = get_var_by_name([self], i)
length_of_nodes_with_matching_names = len(nodes_with_matching_names)
if length_of_nodes_with_matching_names == 0:
raise Exception(f"{i} not found in graph")
else:
if length_of_nodes_with_matching_names > 1:
raise Exception(
f"Found {length_of_nodes_with_matching_names} pytensor variables with name {i}"
)
process_input_to_values[
nodes_with_matching_names[0]
] = inputs_to_values[i]
else:
process_input_to_values[i] = inputs_to_values[i]
return process_input_to_values

inputs_to_values = convert_string_keys_to_variables()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be a bit more "pythonic" like this (I did not test the code!):

Suggested change
def convert_string_keys_to_variables():
process_input_to_values = {}
for i in inputs_to_values:
if isinstance(i, str):
nodes_with_matching_names = get_var_by_name([self], i)
length_of_nodes_with_matching_names = len(nodes_with_matching_names)
if length_of_nodes_with_matching_names == 0:
raise Exception(f"{i} not found in graph")
else:
if length_of_nodes_with_matching_names > 1:
raise Exception(
f"Found {length_of_nodes_with_matching_names} pytensor variables with name {i}"
)
process_input_to_values[
nodes_with_matching_names[0]
] = inputs_to_values[i]
else:
process_input_to_values[i] = inputs_to_values[i]
return process_input_to_values
inputs_to_values = convert_string_keys_to_variables()
def convert_string_keys_to_variables(input_to_values):
new_input_to_values = {}
for key, value in inputs_to_values.items():
if isinstance(key, str):
matching_vars = get_var_by_name([self], key)
if not matching_vars:
raise Exception(f"{key} not found in graph")
elif len(matching_vars) > 1:
raise Exception(
f"Found multiple variables with name {key}"
)
input_to_values[matching_vars[0]] = value
else:
new_input_to_values[key] = value
return new_input_to_values
inputs_to_values = convert_string_keys_to_variables(inputs_to_values)

@Raj-Parekh24
Copy link
Contributor Author

Raj-Parekh24 commented Mar 20, 2023

Thanks for the review @ricardoV94 ,

I have incorporated the suggested changes in the new commit, please review it.

I also wanted to implement a feature under issue #55, the initial points can help me to make things clear.

Copy link
Member

@ricardoV94 ricardoV94 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, running the tests now!

@codecov-commenter
Copy link

Codecov Report

Merging #242 (9008a32) into main (0c9eb9c) will increase coverage by 0.00%.
The diff coverage is 100.00%.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #242   +/-   ##
=======================================
  Coverage   80.44%   80.44%           
=======================================
  Files         170      170           
  Lines       45328    45341   +13     
  Branches    11069    11073    +4     
=======================================
+ Hits        36463    36476   +13     
  Misses       6642     6642           
  Partials     2223     2223           
Impacted Files Coverage Δ
pytensor/graph/basic.py 88.90% <100.00%> (+0.20%) ⬆️

@ricardoV94 ricardoV94 merged commit feccc41 into pymc-devs:main Mar 22, 2023
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow string keys in .eval
3 participants